<?php

/**
 * Log Class
 * Static class that is used to log Messages, Warnings and Errors
 * The Errors are also stored in an error file
 */
class Log {
	//LogEntry types
	const MESSAGE = 'MESSAGE';
	const WARNING = 'WARNING';
	const ERROR = 'ERROR';
	
	//Error log file
	const ERROR_FILE = 'error.txt';
	
	//The log entry storage variable of type ArrayObject
	protected static $_entries;

	/**
	 * Log::slogMessage()
	 * Function that logs a message to the entries array
	 * @param string $msg The message to be logged
	 * @param mixed $vars The variables that need to be logged 
	 */
	public static function logMessage($msg = '', $vars = null)
	{
		self::getEntries()->append(new LogMessage($msg, $vars));
	}
	
	/**
	 * Log::slogWarning()
	 * Function that logs a warning to the entries array
	 * @param string $msg The warning message to be logged
	 */
	public static function logWarning($msg = '')
	{
		self::getEntries()->append(new LogWarning($msg));
	}
	
	/**
	 * Log::slogError()
	 * Function that logs an error to the entries array
	 * @param string $msg The error message to be logged
	 */
	public static function logError($msg = '')
	{
		$logError = new LogError($msg);
		self::getEntries()->append($logError);
		
		//Write the msg to the error file
		$msg = (string) $logError . "\n";
		error_log($msg, 3, self::ERROR_FILE);
	}
	
	/**
	 * Log::getEntries()
	 * Function that returns the entries array
	 * If the array is not created yet it creates a new one
	 * @return ArrayObject All the logged entries are returned
	 */
	public static function getEntries()
	{
		if(!isset(self::$_entries)) self::$_entries = new ArrayObject();

		return self::$_entries;
	}
	
	/**
	 * Log::getEntriesOfType()
	 * Function that lreturns the entries of a given type
	 * @param string $type The entry type of the desired messages
	 */
	public static function getEntriesOfType($type = Log::MESSAGE)
	{
		$entries = new ArrayObject();
		
		foreach (self::getEntries() as $entry) {
			if($entry->getType() === $type){
				$entries->append($entry);
			}
		}
		
		return $entries;
	}
}


/**
 * LogEntry Abstract Class
 * This is the basis of all the messages supposed to be logged in the Log
 * By default each entry will have a time stamp, message, remote address,
 * request uri and a type.
 */
abstract class LogEntry {
	
	protected $_time;
	protected $_msg;
	protected $_uri;
	protected $_remoteAddr;
	protected $_entryType;
	
	function __construct($msg = '', $type = Log::MESSAGE) {
		$this->_time = time();
		$this->_msg = $msg;
		$this->_entryType = $type;
		$this->_uri = $_SERVER['REQUEST_URI'];
		$this->_remoteAddr = $_SERVER['REMOTE_ADDR'];
	}
	
	/**
	 * LogEntry->__toString()
	 * Magical function that returns the string representation of the object
	 * @return string 
	 */
	public function __toString()
	{
		$dateTime = date('Y-m-d H:i:s', $this->_time);
		
		return "[Time: $dateTime][Remote Address: {$this->_remoteAddr}][Uri: {$this->_uri}][{$this->_msg}]";
	}
	
	/**
	 * LogEntry::parse()
	 * Static parse function that tries to extract an array with the entry information within it
	 * @param string $entryStr The string to be parsed
	 * @return array/null 
	 */
	public static function parse($entryStr = '')
	{
		if(preg_match('/^\[Time: (.*)\]\[Remote Address: (.*)\]\[Uri: (.*)\]\[(.*)\]$/', trim($entryStr), $matches)){
			return array( 'time' => $matches[1],
							'remote_addr' => $matches[2],
							'uri' => $matches[3],
							'msg' => $matches[4]
						);
		}
		
		return null;
	}
	
	/**
	 * LogEntry->getType()
	 * Returns the entry type of the log message
	 * @return string The type
	 */
	public function getType(){
		return $this->_entryType;
	}
}

/**
 * LogMessage Class
 * Extends the LogEntry class and has an extra 
 * $_vars for the logged variables
 */
class LogMessage extends LogEntry {
	protected $_vars;
	private $_parentMsg;
	
	function __construct($msg, $vars = null) {
		parent::__construct($msg, Log::MESSAGE);
		
		$this->_parentMsg = parent::__toString();
		$this->_vars = $vars;
	}
	
	/**
	 * Override and extend the functionality of the parent __toString() function
	 */
	public function __toString()
	{
		return  $this->_parentMsg . (!is_null($this->_vars) ? '[Vars: ' . serialize($this->_vars) . ']' : ''); 
	}
	
	/**
	 * Override and extend the functionality of the parent parse() function
	 */
	public static function parse($messageStr = '')
	{
		if(preg_match('/\[Vars: (.*)\]$/', $messageStr, $varsMatch)){
			$vars = unserialize($varsMatch[1]);
			
			if(!is_null($messageLog = LogEntry::parse(str_replace($varsMatch[0], '', $messageStr)))){
				
				$messageLog['vars'] = $vars;
				return $messageLog;
			}
		} else {
			return LogEntry::parse($messageStr);;
		}
	}
}

/**
 * LogWarning Class
 * Extends the LogEntry class
 */
class LogWarning extends LogEntry {

	function __construct($msg) {
		parent::__construct($msg, Log::WARNING);
	}
	
	public static function parse($warningStr = '')
	{
		return LogEntry::parse($warningStr);
	}
}

/**
 * LogError Class
 * Extends the LogEntry class and has an extra 
 * $_backtrace for debugging purposes
 */
class LogError extends LogEntry {
	protected $_backtrace;
	private $_parentMsg;
	
	function __construct($msg) {
		parent::__construct($msg, Log::ERROR);
		
		$this->_parentMsg = parent::__toString();
		$this->_backtrace = debug_backtrace(false);
	}
	
	/**
	 * Override and extend the functionality of the parent __toString() function
	 */
	public function __toString()
	{
		return $this->_parentMsg . "[Trace: " . serialize($this->_backtrace) . "]"; 
	}
	
	/**
	 * Override and extend the functionality of the parent parse() function
	 */
	public static function parse($errorStr = '')
	{
		if(preg_match('/\[Trace: (.*)\]$/', $errorStr, $traceMatch)){
			$trace = unserialize($traceMatch[1]);
			
			if(!is_null($errorLog = LogEntry::parse(str_replace($traceMatch[0], '', $errorStr)))){
				
				$errorLog['trace'] = $trace;
				return $errorLog;
			}
		}
		
		return null;
	}
}